/******************************************************************************
* Copyright 2022 The Airos Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*****************************************************************************/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <regex>
#include <glog/logging.h>

#include "net/udp/udp_impl.h"

namespace air {
namespace net {

/// udp flags
DEFINE_string(net_udp_server_url, "udp://127.0.0.1:8765", "");
DEFINE_string(net_udp_local_url, "udp://127.0.0.1:5678", "");

bool UdpImpl::ParseUrl(
    const std::string& url,
    std::string* ip,
    std::string* port) {
  std::string pattern = R"(^udp://([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):([0-9]+)$)";
  std::regex reg(pattern);
  std::smatch m;
  if (!std::regex_match(url, m, reg)) {
    LOG(ERROR) << "not vaild url: " << url;
    return false;
  }
  if (m.size() != 3) {
    LOG(ERROR) << "match url failed: " << url;
    return false;
  }
  ip->clear();
  ip->append(m[1].str());
  port->clear();
  port->append(m[2].str());
  return true;
}

bool UdpImpl::BuildAddr(const std::string& url, struct sockaddr_in* addr) {
  std::string ip;
  std::string port;
  if (!ParseUrl(url, &ip, &port)) {
    return false;
  }
  int port_num = -1;
  try {
    port_num = std::stoi(port);
  } catch (std::exception &e) {
    LOG(ERROR) << "convert port to num failed, " << port;
    return false;
  }
  bzero(addr, sizeof(struct sockaddr_in));
  addr->sin_family = AF_INET;
  inet_pton(AF_INET, ip.c_str(), &addr->sin_addr);
  addr->sin_port = htons(port_num);
  return true;
}

std::shared_ptr<UdpImpl> UdpImpl::GetInstance() {
    static std::shared_ptr<UdpImpl> s_instance_;
    static std::once_flag s_init_;
    std::call_once(s_init_, [](){
      s_instance_.reset(new (std::nothrow)UdpImpl());
      s_instance_->SetOption(
          air::net::NetOption::kServerUrl,
          FLAGS_net_udp_server_url);
      s_instance_->SetOption(
          air::net::NetOption::kLocalUrl,
          FLAGS_net_udp_local_url);
      s_instance_->Connect();
    });
    return s_instance_;
}

int UdpImpl::SetOption(NetOption opt, const std::string& val) {
  int re = 0;
  switch (opt) {
  case NetOption::kLocalUrl:
    if (!BuildAddr(val, &local_addr_)) {
      re = -1;
    }
    break;
  case NetOption::kServerUrl:
    if (!BuildAddr(val, &target_addr_)) {
      re = -1;
    }
    break;
  default:
    re = -1;
    LOG(ERROR) << "unknown net option " << static_cast<int>(opt);
    break;
  }
  return re;
}

int UdpImpl::Connect() {
  fd_ = socket(AF_INET, SOCK_DGRAM, 0);
  if (fd_ < 0) {
    LOG(ERROR) << "create udp socket failed: " << strerror(errno);
    return -1;
  }
  int opt = 1;
  setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt, sizeof(opt));
  if (local_addr_.sin_port != 0) {
    if (0 > bind(
                fd_,
                reinterpret_cast<sockaddr *>(&local_addr_),
                sizeof(local_addr_))) {
      LOG(ERROR) << "bind udp socket failed: " << strerror(errno);
      return -1;
    }
  }
  return 0;
}

int UdpImpl::Send(const std::string& dst, const std::string& data) {
  return sendto(
      fd_,
      data.data(),
      data.size(), 0,
      reinterpret_cast<sockaddr *>(&target_addr_),
      sizeof(target_addr_));
}

int UdpImpl::Recv(std::string* src, std::string* data) {
  if (data == nullptr) {
    LOG(ERROR) << "data pointer is nullptr";
    return -1;
  }
  data->clear();
  int ret = recvfrom(fd_, buf_, sizeof(buf_), 0, NULL, 0);
  if (ret <= 0) {
    LOG(ERROR) << "recvfrom udp socket failed: " << strerror(errno);
    return -1;
  }
  data->append(buf_, ret);
  return 0;
}

}  // namespace net
}  // namespace air
